<template>
  <div style="height: 100%; width: 100%;">
    <!-- 决策树 -->
    <div class="tree">
      <div class="m_title">
        <div class="divider"></div>
        <strong>模型结果</strong>
      </div>
      <div class="right-text" align="right">
        {{
          `${modelData.param && modelData.param.label_col}~${
            modelData.data && modelData.data.feature_list.toString()
          }`
        }}
        <div class="detail">
          {{
            `${modelData.param && modelData.param.label_col}~${
              modelData.data && modelData.data.feature_list.toString()
            }`
          }}
        </div>
      </div>
      <div class="result">
        <a-button class="full-screen-icon" @click="fullScreen()">
          <a-icon-font type="iconfangdahuabanquanping"></a-icon-font>
        </a-button>
        <div class="left-block four-scroll-y" align="center">
          <div class="decision-tree" :style="{ width: `${max_width}px` }">
            <div
              v-for="(a, row) in tree_depth"
              :key="`row${row}`"
              :style="{
                position: 'absolute',
                top: `${row * (space_y + leaf_height) + space_y / 2}px`,
                width: `${max_width}px`,
                height: `${leaf_height}px`,
              }"
            >
              <div v-for="(b, col) in pow(row, 2)" :key="`row${row}-col${col}`">
                <div
                  v-show="tree_data[row][col] !== -1"
                  class="leaf"
                  :style="getLeafPosition(row, col)"
                >
                  <div
                    v-if="row === 1"
                    style="margin-top: -20px; padding: 0 20%;"
                    :align="col % 2 === 0 ? 'left' : 'right'"
                  >
                    {{ col % 2 === 0 ? 'True' : 'False' }}
                  </div>
                  <p>{{ getLeafContent(row, col)[0] }}</p>
                  <p>{{ getLeafContent(row, col)[1] }}</p>
                  <p
                    v-if="tree_structure.is_leaves[tree_data[row][col]]"
                    :style="{ 'line-height': `${leaf_height}px` }"
                  >
                    {{ tree_structure.classes[tree_data[row][col]] }}
                  </p>
                </div>
              </div>
            </div>
            <!-- 连接线 -->
            <line-of-relational-connection
              v-for="(edge, index) in getEdgeInfo()"
              :key="`edge${index}`"
              :edge="edge"
              :node-height="leaf_height"
              :node-width="leaf_width"
              :difference-of-grid-width="0"
              :difference-of-grid-height="0"
            />
          </div>
        </div>
        <div class="right-block">
          <p>树的深度：{{ modelData.param && modelData.param.max_depth }}</p>
          <p>
            节点数量：{{
              modelData.data && modelData.data.tree_structure
                ? modelData.data.tree_structure.n_nodes
                : '-'
            }}
          </p>
          <p>Feature Importance：</p>
          <table style="width: 100%;">
            <tr>
              <th scope="row"></th>
              <th scope="row">features</th>
              <th scope="row">importance</th>
            </tr>
            <tr
              v-for="(item, index) in featureImportance"
              :key="`feature${index}`"
            >
              <td>{{ index }}</td>
              <td>{{ item.feature }}</td>
              <td>{{ item.importance }}</td>
            </tr>
          </table>
        </div>
      </div>

      <div class="m_title">
        <div class="divider"></div>
        <strong>整体评估</strong>
      </div>
      <div class="access">
        <a-row>
          <a-col
            v-for="item in circelArray"
            :key="item.title"
            :span="6"
            class="circle-block"
          >
            <svg class="c-c-c">
              <circle
                cx="40"
                cy="40"
                r="35"
                stroke="#eee"
                stroke-width="8"
                fill="none"
                :stroke-dashoffset="`${80 * Math.PI}px`"
              />
              <circle
                cx="40"
                cy="40"
                r="35"
                stroke="#78C06E"
                stroke-width="8"
                fill="none"
                :stroke-dasharray="item.dasharray"
                class="blue-circle progress"
              />
              <text x="15" y="5" fill="#5D637E" transform="rotate(90 20,20)">
                {{ item.percent }}
              </text>
            </svg>
            <!-- <Chart :chart-type="'pieChart'" :config="commonConfig" /> -->
            <!-- <div class="circle-title">
              <b>{{ item.title }}</b
              ><a-icon-font type="iconinfo-cirlce-o" class="icon"></a-icon-font>
              <p>{{ item.english }}</p>
            </div> -->
            <a-tooltip placement="right">
              <template slot="title">
                <span>{{ item.explain }}</span>
              </template>
              <div class="circle-title">
                <b>{{ item.title }}</b
                ><a-icon-font
                  type="iconinfo-cirlce-o"
                  class="icon"
                ></a-icon-font>
                <p>{{ item.english }}</p>
              </div>
            </a-tooltip>
          </a-col>
        </a-row>
      </div>

      <div class="m_title">
        <div class="divider"></div>
        <strong>详细评估</strong>
      </div>
      <a-row>
        <a-col :span="12" style="text-align: left;">Confusion Matrix：</a-col>
        <a-col :span="12" style="text-align: right;"
          >{{
            `${modelData.param && modelData.param.label_col}={${
              labelListString
                ? labelListString
                : modelData.data && modelData.data.label_list.toString()
            }` + '}'
          }}
        </a-col>
      </a-row>
      <table
        v-if="modelData.data && modelData.data.label_list"
        class="dtc_table"
        style="table-layout: fixed;"
      >
        <tr>
          <th :colspan="24 / modelData.data.label_list.length" scope="row">
            <svg style="height: 44px; vertical-align: middle; width: 100%;">
              <line x1="0" y1="0" x2="100%" y2="100%" stroke="#e8e8e8" />
              <text x="10" y="40" fill="black">Pred</text>
              <text x="95%" text-anchor="end" y="20" fill="black">True</text>
            </svg>
          </th>
          <th
            v-for="item in modelData.data && modelData.data.label_list"
            :key="item"
            :colspan="24 / modelData.data.label_list.length"
            scope="row"
          >
            {{ item }}
          </th>
        </tr>
        <tr
          v-for="(y, row) in modelData.data && modelData.data.label_list"
          :key="`label${row}`"
        >
          <td :colspan="24 / modelData.data.label_list.length">{{ y }}</td>
          <td
            v-for="(item, index) in modelData.data &&
            modelData.data.confusion_matrix[row]"
            :key="`matrix${index}`"
            :colspan="24 / modelData.data.label_list.length"
          >
            {{ item }}
          </td>
        </tr>
      </table>
    </div>

    <a-modal
      title="决策树"
      :visible="fullScreenVisible"
      ok-text="确定"
      cancel-text="取消"
      centered
      width="95%"
      @ok="fullScreenVisible = false"
      @cancel.stop="fullScreenVisible = false"
    >
      <div v-show="fullScreenVisible" style="overflow: auto;">
        <MyDecisionTree
          :leafs="myTree"
          @finish-render="finishRender"
        ></MyDecisionTree>
      </div>
    </a-modal>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
// 连线
import LineOfRelationalConnection from '@/components/studio/data/model/Line.vue'
import { toInteger } from 'lodash'
import DataViewStore from '@/store/modules/dataview'
import DrawDecisionTree from '@/components/studio/data/model/DrawDecisionTree.vue'
import MyDecisionTree from '@/components/studio/data/model/MyDecisionTree.vue'
import Chart from '@/components/chart/chart.vue'

@Component({
  components: {
    LineOfRelationalConnection,
    DrawDecisionTree,
    MyDecisionTree,
    Chart,
  },
})
export default class DecisionTree extends Vue {
  public featureImportance: any = []
  public data: any = []
  public featureString: string | null = null
  public labelListString: string | null = null

  // tree
  public node_number: number = 0
  public tree_depth: number = 0
  public tree_structure: any = {}
  public leaf_width: number = 140
  public max_width: number = 0
  public max_height: number = 0
  public leaf_height: number = 44
  public space_x: number = 10
  public space_y: number = 40
  public tree_data: any = []
  public circelArray: any = []
  public fullScreenVisible: boolean = false
  private chartData: any[] = []
  public myTree: any = {}

  public get commonConfig() {
    return {
      value: this.chartData,
      labelKey: 'title',
      valueKey: 'percent',
      titleIsShow: false,
      xAxisFontSize: 12,
      size: [372, 372],
      labelIsShow: false,
      legendIsShow: false,
    }
  }

  public get modelData() {
    return DataViewStore.selectModel
  }

  public finishRender() {
    // this.spinning = false
    // this.fullScreenVisible = true
  }

  public fullScreen() {
    this.fullScreenVisible = true
  }

  @Watch('modelData.data')
  public getModelData() {
    this.init()
  }

  public created() {
    this.init()
  }

  public init() {
    const { data } = this.modelData
    this.featureString = this.modelData?.data?.feature_list?.toString()
    this.featureImportance = []
    data?.feature_list.forEach((element: any, index: any) => {
      this.featureImportance.push({
        feature: element,
        importance: data?.feature_importances[index],
      })
    })
    this.circelArray = []
    this.circelArray.push(
      {
        title: '准确率',
        english: 'Accuracy',
        explain: '预测正确的样本数，占总样本数的比重。',
        percent: data ? `${(data.accuracy_test * 100).toFixed(2)}%` : '-',
        dasharray: `${data.accuracy_test * 70 * Math.PI}px,${70 * Math.PI}px`, // 70 为直径
      },
      {
        title: 'F1-值',
        english: 'F1-score',
        explain:
          'F1值为算数平均数除以几何平均数，且越大越好，将Precision和Recall的上述公式带入会发现，当F1值小时，True Positive相对增加，而false相对减少，即Precision和Recall都相对增加，即F1对Precision和Recall都进行了加权。',
        percent: data ? `${(data.Fmeasure_test * 100).toFixed(2)}%` : '-',
        dasharray: `${data.Fmeasure_test * 70 * Math.PI}px,${70 * Math.PI}px`,
      },
      {
        title: '精确率',
        english: 'Precision',
        explain:
          '查准率，即正确预测为正的，占全部预测为正的比例。*此处为macro平均',
        percent: data ? `${(data.precision_test * 100).toFixed(2)}%` : '-',
        dasharray: `${data.precision_test * 70 * Math.PI}px,${70 * Math.PI}px`,
      },
      {
        title: '召回率',
        english: 'Recall',
        explain:
          '查全率，即正确预测为正的，占全部实际为正的比例。*此处为macro平均',
        percent: data ? `${(data.recall_test * 100).toFixed(2)}%` : '-',
        dasharray: `${data.recall_test * 70 * Math.PI}px,${70 * Math.PI}px`,
      }
    )
    this.labelListString = `${data?.label_list?.toString()}`
    this.drawTree()
  }

  public drawTree() {
    this.tree_structure = this.modelData?.data?.tree_structure
    this.tree_depth = this.tree_structure?.max_depth
    this.node_number = this.tree_structure?.n_nodes
    this.max_width =
      this.leaf_width * this.pow(this.tree_depth - 1, 2) +
      this.space_x * this.pow(this.tree_depth - 1, 2)
    this.max_height =
      this.leaf_height * this.tree_depth +
      this.space_x * (this.tree_depth - 1) +
      this.space_y / 2

    const tree: any = []
    for (let i = 0; i < this.tree_depth; i += 1) {
      const floor = new Array(this.pow(i, 2))
      tree.push(floor)
    }
    tree[0][0] = 0
    const nodes = this.tree_structure.node_depth
    const left = this.tree_structure.children_left
    const right = this.tree_structure.children_right
    const { feature, threshold } = this.tree_structure

    this.myTree = {
      name: 0,
      feature: feature[0] > -2 ? this.modelData.data?.feature_list[0] : '',
      threshold: threshold[0] > -2 ? `<= ${threshold[0]}` : '',
      // collapsed: true, // true默认收起 false展开
      children: [],
    }
    this.drawStructure(this.myTree)
    nodes.forEach((element: any, row: any) => {
      if (row < this.tree_depth - 1) {
        for (let col = 0; col < this.pow(row, 2); col += 1) {
          tree[row + 1][col * 2] =
            tree[row][col] !== -1 ? left[tree[row][col]] : -1
          tree[row + 1][col * 2 + 1] =
            tree[row][col] !== -1 ? right[tree[row][col]] : -1
        }
      }
    })
    this.tree_data = tree
  }

  public drawStructure(drawTree: any) {
    const left = this.tree_structure.children_left
    const right = this.tree_structure.children_right
    const { feature, threshold, classes } = this.tree_structure

    // variableName: 条件, label: name
    if (drawTree.name !== -1) {
      const featureLeft = feature[left[drawTree.name]]
      const featureRight = feature[right[drawTree.name]]
      drawTree.children = []

      if (left[drawTree.name] > -1) {
        const leftChild = {
          name: left[drawTree.name],
          feature:
            featureLeft > -2
              ? this.modelData.data?.feature_list[featureLeft]
              : classes[left[drawTree.name]],
          threshold:
            threshold[left[drawTree.name]] > -2
              ? `<=${threshold[left[drawTree.name]]}`
              : '',
        }
        drawTree.children.push(leftChild)
        this.drawStructure(leftChild)
      }
      if (right[drawTree.name] > -1) {
        const rightChild = {
          name: right[drawTree.name],
          feature:
            featureRight > -2
              ? this.modelData.data?.feature_list[featureRight]
              : classes[right[drawTree.name]],
          threshold:
            threshold[right[drawTree.name]] > -2
              ? `<=${threshold[right[drawTree.name]]}`
              : '',
        }
        drawTree.children.push(rightChild)
        this.drawStructure(rightChild)
      }
    }
  }

  public getLeafPosition(row: number, col: number) {
    const left =
      (this.max_width / this.pow(row, 2)) * col +
      this.max_width / this.pow(row + 1, 2) -
      this.leaf_width / 2
    const top = 0 // this.space_y*row
    return {
      left: `${left}px`,
      top: `${top}px`,
      // background: `${this.rgbColor()}`,
    }
  }

  public getStartEndPos(row: number, col: number) {
    return Number(this.getLeafPosition(row, col).left.replace(/px/, ''))
  }

  public getEdgeInfo() {
    const edgeInfo: any = []
    for (let row = 0; row < this.tree_depth - 1; row += 1) {
      for (let col = 0; col <= this.pow(row + 1, 2) - 1; col += 1) {
        if (this.tree_data[row + 1][col] === -1) {
          edgeInfo.push(false)
        } else {
          const left = col % 2 === 0
          const item = {
            x:
              (left
                ? this.max_width / this.pow(row + 2, 2) - this.leaf_width / 2
                : this.max_width / this.pow(row + 1, 2)) +
              (toInteger(col / 2) * this.max_width) / this.pow(row, 2),
            y:
              this.leaf_height * (row + 1) +
              this.space_y / 2 +
              row * this.space_y,
            startEndPos: {
              xStart: left
                ? this.max_width / this.pow(row + 2, 2) + this.leaf_width / 2
                : 0,
              yStart: 0,
              xEnd: !left
                ? this.max_width / this.pow(row + 2, 2) + this.leaf_width / 2
                : 0,
              yEnd: this.space_y,
            },
            isLeft: left,
          }
          edgeInfo.push(item)
        }
      }
    }
    return edgeInfo
  }

  public getLeafContent(row: any, col: any) {
    const feature = this.tree_structure.feature[this.tree_data[row][col]]
    const threshold = this.tree_structure.threshold[this.tree_data[row][col]]
    if (feature > -2) {
      if (threshold > -2) {
        return [this.modelData.data?.feature_list[feature], `<=${threshold}`]
        // return [{name:`${this.modelData.data?.feature_list[feature]}<=${threshold}`}]
      }
    }
    return ''
  }

  public pow(n: number, x: number) {
    if (n > 0) {
      for (let i = n; i > 1; i -= 1) {
        x *= 2
      }
      return x
    }
    return 1
  }

  public rgbColor() {
    const r = Math.floor(Math.random() * 256)
    const g = Math.floor(Math.random() * 256)
    const b = Math.floor(Math.random() * 256)
    const rgb = `rgb(${r},${g},${b})`
    return rgb
  }
}
</script>

<style lang="less" scoped>
@import '@/styles/scroll-bar';

.m_title {
  align-items: center;
  display: flex;
  margin-bottom: 22px;

  b {
    font-size: 16px;
  }
}

.divider {
  background-color: #6973ff;
  height: 18px;
  margin-right: 8px;
  width: 4px;
}

.right-text {
  margin-bottom: 8px;
  margin-left: calc(70% - 48px);
  margin-top: -6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
  width: 30vw;

  .detail {
    background: rgba(0, 0, 0, 0.6);
    border-radius: 2px;
    box-shadow: 0 9px 28px 8px rgba(0, 0, 0, 0.05),
      0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12);
    color: #fff;
    display: none;
    max-width: 90%;
    padding: 8px;
    position: absolute;
    right: 24px;
    top: 315px;
    white-space: normal;
    z-index: 99;
  }

  &:hover {
    .detail {
      display: inline-block;
    }
  }
}

.tree {
  width: 100%;

  .result {
    border: 1px solid #e8e8e8;
    display: flex;
    margin-bottom: 24px;
    min-height: 260px;
    position: relative;
    width: 100%;

    .full-screen-icon {
      border: 0;
      box-shadow: 0 !important;
      position: absolute;
      right: calc(32% + 10px);
      top: 5px;
      z-index: 100;
    }

    &:hover {
      .full-screen-icon {
        display: inline-block;
      }
    }

    .left-block {
      border-right: 1px solid #e8e8e8;
      overflow: auto;
      position: relative;
      width: 68%;

      .decision-tree {
        min-height: 100px;
        position: relative;
      }

      .leaf {
        border: 1px solid #d3d9e6;
        border-radius: 4px;
        height: 44px;
        position: absolute;
        width: 140px;

        p {
          margin: 0;
        }
      }
    }

    .right-block {
      padding: 7px 20px;
      width: 32%;

      p {
        margin: 0;
        margin-bottom: 12px;
      }
    }
  }

  .access {
    border: 1px solid #e8e8e8;
    height: 128px;
    margin-bottom: 24px;
    width: 100%;

    .circle-block {
      align-items: center;
      border-right: 1px solid #e8e8e8;
      display: flex;
      height: 100px;
      justify-content: center;
      margin-top: 12px;

      .c-c-c {
        height: 80px;
        transform: rotate(-90deg);
        width: 80px;
      }

      .blue-circle {
        transform-origin: 50% 50%;
      }

      .circle-title {
        margin-left: 16px;

        .icon {
          font-size: 14px;
          margin-left: 8px;
        }
      }
    }
  }
}

.dtc_table {
  border-collapse: collapse;
  border-color: #e8e8e8;
  line-height: 36px;
  margin-top: 8px;
  min-height: 36px;
  text-align: center;
  width: 100%;

  th {
    background: #fafafa;
  }
}

.dtc_table tr th,
.dtc_table tr td {
  border: 1px solid #e8e8e8;
}

.progress {
  animation: rotate 1500ms linear both;
}
</style>
